{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Ifon2ScEAsJO"
      },
      "source": [
        "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n",
        "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/transform/simple\"\u003e\n",
        "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n",
        "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/transform/simple.ipynb\"\u003e\n",
        "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n",
        "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/blob/master/docs/tutorials/transform/simple.ipynb\"\u003e\n",
        "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n",
        "\u003c/table\u003e\u003c/div\u003e"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "tghWegsjhpkt"
      },
      "source": [
        "##### Copyright &copy; 2020 Google Inc."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "cellView": "form",
        "colab": {},
        "colab_type": "code",
        "id": "rSGJWC5biBiG"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "mPt5BHTwy_0F"
      },
      "source": [
        "# Preprocess data with TensorFlow Transform\n",
        "***The Feature Engineering Component of TensorFlow Extended (TFX)***\n",
        "\n",
        "This example colab notebook provides a very simple example of how \u003ca target='_blank' href='https://www.tensorflow.org/tfx/transform/'\u003eTensorFlow Transform (\u003ccode\u003etf.Transform\u003c/code\u003e)\u003c/a\u003e can be used to preprocess data using exactly the same code for both training a model and serving inferences in production.\n",
        "\n",
        "TensorFlow Transform is a library for preprocessing input data for TensorFlow, including creating features that require a full pass over the training dataset.  For example, using TensorFlow Transform you could:\n",
        "\n",
        "* Normalize an input value by using the mean and standard deviation\n",
        "* Convert strings to integers by generating a vocabulary over all of the input values\n",
        "* Convert floats to integers by assigning them to buckets, based on the observed data distribution\n",
        "\n",
        "TensorFlow has built-in support for manipulations on a single example or a batch of examples. `tf.Transform` extends these capabilities to support full passes over the entire training dataset.\n",
        "\n",
        "The output of `tf.Transform` is exported as a TensorFlow graph which you can use for both training and serving. Using the same graph for both training and serving can prevent skew, since the same transformations are applied in both stages."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "RptgLn2RYuK3"
      },
      "source": [
        "## Python check and imports\n",
        "First, we'll make sure that we're using Python 3. Then, we'll go ahead and install and import the stuff we need."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "tFcdSuXTidhH"
      },
      "outputs": [],
      "source": [
        "import sys\n",
        "\n",
        "# Confirm that we're using Python 3\n",
        "assert sys.version_info.major is 3, 'Oops, not running Python 3. Use Runtime \u003e Change runtime type'"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "K4QXVIM7iglN"
      },
      "outputs": [],
      "source": [
        "import argparse\n",
        "import os\n",
        "import pprint\n",
        "import tempfile\n",
        "import urllib.request\n",
        "import zipfile\n",
        "\n",
        "print(\"Installing dependencies for Colab environment\")\n",
        "!pip install -Uq grpcio==1.26.0\n",
        "\n",
        "import tensorflow as tf\n",
        "\n",
        "print('Installing Apache Beam')\n",
        "!pip install -Uq apache_beam==2.16.0\n",
        "import apache_beam as beam\n",
        "\n",
        "print('Installing TensorFlow Transform')\n",
        "!pip install -Uq tensorflow-transform==0.15.0\n",
        "import tensorflow_transform as tft\n",
        "\n",
        "import apache_beam.io.iobase\n",
        "import tensorflow_transform.beam as tft_beam\n",
        "from tensorflow_transform.tf_metadata import dataset_metadata\n",
        "from tensorflow_transform.tf_metadata import dataset_schema"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "CxOxaaOYRfl7"
      },
      "source": [
        "## Data: Create some dummy data\n",
        "We'll create some simple dummy data for our simple example:\n",
        "\n",
        "* `raw_data` is the initial raw data that we're going to preprocess\n",
        "* `raw_data_metadata` contains the schema that tells us the types of each of the columns in `raw_data`.  In this case, it's very simple."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "-R236Tkf_ON3"
      },
      "outputs": [],
      "source": [
        "raw_data = [\n",
        "      {'x': 1, 'y': 1, 's': 'hello'},\n",
        "      {'x': 2, 'y': 2, 's': 'world'},\n",
        "      {'x': 3, 'y': 3, 's': 'hello'}\n",
        "  ]\n",
        "\n",
        "raw_data_metadata = dataset_metadata.DatasetMetadata(\n",
        "    dataset_schema.from_feature_spec({\n",
        "        'y': tf.io.FixedLenFeature([], tf.float32),\n",
        "        'x': tf.io.FixedLenFeature([], tf.float32),\n",
        "        's': tf.io.FixedLenFeature([], tf.string),\n",
        "    }))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Zadh6MXLS3eD"
      },
      "source": [
        "## Transform: Create a preprocessing function\n",
        "The _preprocessing function_ is the most important concept of tf.Transform. A preprocessing function is where the transformation of the dataset really happens. It accepts and returns a dictionary of tensors, where a tensor means a \u003ca target='_blank' href='https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/Tensor'\u003e\u003ccode\u003eTensor\u003c/code\u003e\u003c/a\u003e or \u003ca target='_blank' href='https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/SparseTensor'\u003e\u003ccode\u003eSparseTensor\u003c/code\u003e\u003c/a\u003e. There are two main groups of API calls that typically form the heart of a preprocessing function:\n",
        "\n",
        "1. **TensorFlow Ops:** Any function that accepts and returns tensors, which usually means TensorFlow ops. These add TensorFlow operations to the graph that transforms raw data into transformed data one feature vector at a time.  These will run for every example, during both training and serving.\n",
        "2. **TensorFlow Transform Analyzers:** Any of the analyzers provided by tf.Transform. Analyzers also accept and return tensors, but unlike TensorFlow ops they only run once, during training, and typically make a full pass over the entire training dataset. They create \u003ca target='_blank' href='https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/constant'\u003etensor constants\u003c/a\u003e, which are added to your graph. For example, `tft.min` computes the minimum of a tensor over the training dataset. tf.Transform provides a fixed set of analyzers, but this will be extended in future versions.\n",
        "\n",
        "Caution: When you apply your preprocessing function to serving inferences, the constants that were created by analyzers during training do not change.  If your data has trend or seasonality components, plan accordingly."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "H2wANNF_2dCR"
      },
      "outputs": [],
      "source": [
        "def preprocessing_fn(inputs):\n",
        "    \"\"\"Preprocess input columns into transformed columns.\"\"\"\n",
        "    x = inputs['x']\n",
        "    y = inputs['y']\n",
        "    s = inputs['s']\n",
        "    x_centered = x - tft.mean(x)\n",
        "    y_normalized = tft.scale_to_0_1(y)\n",
        "    s_integerized = tft.compute_and_apply_vocabulary(s)\n",
        "    x_centered_times_y_normalized = (x_centered * y_normalized)\n",
        "    return {\n",
        "        'x_centered': x_centered,\n",
        "        'y_normalized': y_normalized,\n",
        "        's_integerized': s_integerized,\n",
        "        'x_centered_times_y_normalized': x_centered_times_y_normalized,\n",
        "    }"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "cSl9qyTCbBKR"
      },
      "source": [
        "## Putting it all together\n",
        "Now we're ready to transform our data.  We'll use Apache Beam with a direct runner, and supply three inputs:\n",
        "1. `raw_data` - The raw input data that we created above\n",
        "2. `raw_data_metadata` - The schema for the raw data\n",
        "3. `preprocessing_fn` - The function that we created to do our transformation\n",
        "\n",
        "\u003caside class=\"key-term\"\u003e\u003cb\u003eKey Term:\u003c/b\u003e \u003ca target='_blank' href='https://beam.apache.org/'\u003eApache Beam\u003c/a\u003e uses a \u003ca target='_blank' href='https://beam.apache.org/documentation/programming-guide/#applying-transforms'\u003especial syntax to define and invoke transforms\u003c/a\u003e.  For example, in this line:\n",
        "\n",
        "\u003ccode\u003e\u003cblockquote\u003eresult = pass_this | 'name this step' \u003e\u003e to_this_call\u003c/blockquote\u003e\u003c/code\u003e\n",
        "\n",
        "The method \u003ccode\u003eto_this_call\u003c/code\u003e is being invoked and passed the object called \u003ccode\u003epass_this\u003c/code\u003e, and \u003ca target='_blank' href='https://stackoverflow.com/questions/50519662/what-does-the-redirection-mean-in-apache-beam-python'\u003ethis operation will be referred to as \u003ccode\u003ename this step\u003c/code\u003e in a stack trace\u003c/a\u003e.  The result of the call to \u003ccode\u003eto_this_call\u003c/code\u003e is returned in \u003ccode\u003eresult\u003c/code\u003e.  You will often see stages of a pipeline chained together like this:\n",
        "\n",
        "\u003ccode\u003e\u003cblockquote\u003eresult = apache_beam.Pipeline() | 'first step' \u003e\u003e do_this_first() | 'second step' \u003e\u003e do_this_last()\u003c/blockquote\u003e\u003c/code\u003e\n",
        "\n",
        "and since that started with a new pipeline, you can continue like this:\n",
        "\n",
        "\u003ccode\u003e\u003cblockquote\u003enext_result = result | 'doing more stuff' \u003e\u003e another_function()\u003c/blockquote\u003e\u003c/code\u003e\u003c/aside\u003e"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "mAF9w7RTZU7c"
      },
      "outputs": [],
      "source": [
        "def main():\n",
        "  # Ignore the warnings\n",
        "  with tft_beam.Context(temp_dir=tempfile.mkdtemp()):\n",
        "    transformed_dataset, transform_fn = (  # pylint: disable=unused-variable\n",
        "        (raw_data, raw_data_metadata) | tft_beam.AnalyzeAndTransformDataset(\n",
        "            preprocessing_fn))\n",
        "\n",
        "  transformed_data, transformed_metadata = transformed_dataset  # pylint: disable=unused-variable\n",
        "\n",
        "  print('\\nRaw data:\\n{}\\n'.format(pprint.pformat(raw_data)))\n",
        "  print('Transformed data:\\n{}'.format(pprint.pformat(transformed_data)))\n",
        "\n",
        "if __name__ == '__main__':\n",
        "  main()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "NO6LyTneNndy"
      },
      "source": [
        "## Is this the right answer?\n",
        "Previously, we used `tf.Transform` to do this:\n",
        "```\n",
        "x_centered = x - tft.mean(x)\n",
        "y_normalized = tft.scale_to_0_1(y)\n",
        "s_integerized = tft.compute_and_apply_vocabulary(s)\n",
        "x_centered_times_y_normalized = (x_centered * y_normalized)\n",
        "```\n",
        "####x_centered\n",
        "With input of `[1, 2, 3]` the mean of x is 2, and we subtract it from x to center our x values at 0.  So our result of `[-1.0, 0.0, 1.0]` is correct.\n",
        "####y_normalized\n",
        "We wanted to scale our y values between 0 and 1.  Our input was `[1, 2, 3]` so our result of `[0.0, 0.5, 1.0]` is correct.\n",
        "####s_integerized\n",
        "We wanted to map our strings to indexes in a vocabulary, and there were only 2 words in our vocabulary (\"hello\" and \"world\").  So with input of `[\"hello\", \"world\", \"hello\"]` our result of `[0, 1, 0]` is correct.\n",
        "####x_centered_times_y_normalized\n",
        "We wanted to create a new feature by crossing `x_centered` and `y_normalized` using multiplication.  Note that this multiplies the results, not the original values, and our new result of `[-0.0, 0.0, 1.0]` is correct."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "tghWegsjhpkt"
      ],
      "name": "simple.ipynb",
      "private_outputs": true,
      "provenance": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
